// Copyright (c) 2021, gottingen group.
// All rights reserved.
// Created by liyinbin lijippy@163.com

#include "abel/container/node_hash_map.h"
#include "abel/container/internal/tracked.h"
#include "unordered_map_constructor_test.h"
#include "unordered_map_lookup_test.h"
#include "unordered_map_members_test.h"
#include "unordered_map_modifiers_test.h"

namespace abel {

    namespace container_internal {
        namespace {

            using ::testing::Field;
            using ::testing::IsEmpty;
            using ::testing::Pair;
            using ::testing::UnorderedElementsAre;

            using MapTypes = ::testing::Types<
                    abel::node_hash_map<int, int, StatefulTestingHash, StatefulTestingEqual,
                            Alloc<std::pair<const int, int>>>,
                    abel::node_hash_map<std::string, std::string, StatefulTestingHash,
                            StatefulTestingEqual,
                            Alloc<std::pair<const std::string, std::string>>>>;

            INSTANTIATE_TYPED_TEST_SUITE_P(NodeHashMap, ConstructorTest, MapTypes);
            INSTANTIATE_TYPED_TEST_SUITE_P(NodeHashMap, LookupTest, MapTypes);
            INSTANTIATE_TYPED_TEST_SUITE_P(NodeHashMap, MembersTest, MapTypes);
            INSTANTIATE_TYPED_TEST_SUITE_P(NodeHashMap, ModifiersTest, MapTypes);

            using M = abel::node_hash_map<std::string, Tracked<int>>;

            TEST(NodeHashMap, Emplace) {
                M m;
                Tracked<int> t(53);
                m.emplace("a", t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(1, t.num_copies());

                m.emplace(std::string("a"), t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(1, t.num_copies());

                std::string a("a");
                m.emplace(a, t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(1, t.num_copies());

                const std::string ca("a");
                m.emplace(a, t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(1, t.num_copies());

                m.emplace(std::make_pair("a", t));
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(2, t.num_copies());

                m.emplace(std::make_pair(std::string("a"), t));
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(3, t.num_copies());

                std::pair<std::string, Tracked<int>> p("a", t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(4, t.num_copies());
                m.emplace(p);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(4, t.num_copies());

                const std::pair<std::string, Tracked<int>> cp("a", t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(5, t.num_copies());
                m.emplace(cp);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(5, t.num_copies());

                std::pair<const std::string, Tracked<int>> pc("a", t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(6, t.num_copies());
                m.emplace(pc);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(6, t.num_copies());

                const std::pair<const std::string, Tracked<int>> cpc("a", t);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(7, t.num_copies());
                m.emplace(cpc);
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(7, t.num_copies());

                m.emplace(std::piecewise_construct, std::forward_as_tuple("a"),
                          std::forward_as_tuple(t));
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(7, t.num_copies());

                m.emplace(std::piecewise_construct, std::forward_as_tuple(std::string("a")),
                          std::forward_as_tuple(t));
                ASSERT_EQ(0, t.num_moves());
                ASSERT_EQ(7, t.num_copies());
            }

            TEST(NodeHashMap, AssignRecursive) {
                struct Tree {
                    // Verify that unordered_map<K, IncompleteType> can be instantiated.
                    abel::node_hash_map<int, Tree> children;
                };
                Tree root;
                const Tree &child = root.children.emplace().first->second;
                // Verify that `lhs = rhs` doesn't read rhs after clearing lhs.
                root = child;
            }

            TEST(FlatHashMap, MoveOnlyKey) {
                struct Key {
                    Key() = default;

                    Key(Key &&) = default;

                    Key &operator=(Key &&) = default;
                };
                struct Eq {
                    bool operator()(const Key &, const Key &) const { return true; }
                };
                struct Hash {
                    size_t operator()(const Key &) const { return 0; }
                };
                abel::node_hash_map<Key, int, Hash, Eq> m;
                m[Key()];
            }

            struct NonMovableKey {
                explicit NonMovableKey(int i) : i(i) {}

                NonMovableKey(NonMovableKey &&) = delete;

                int i;
            };

            struct NonMovableKeyHash {
                using is_transparent = void;

                size_t operator()(const NonMovableKey &k) const { return k.i; }

                size_t operator()(int k) const { return k; }
            };

            struct NonMovableKeyEq {
                using is_transparent = void;

                bool operator()(const NonMovableKey &a, const NonMovableKey &b) const {
                    return a.i == b.i;
                }

                bool operator()(const NonMovableKey &a, int b) const { return a.i == b; }
            };

            TEST(NodeHashMap, MergeExtractInsert) {
                abel::node_hash_map<NonMovableKey, int, NonMovableKeyHash, NonMovableKeyEq>
                        set1, set2;
                set1.emplace(std::piecewise_construct, std::make_tuple(7),
                             std::make_tuple(-7));
                set1.emplace(std::piecewise_construct, std::make_tuple(17),
                             std::make_tuple(-17));

                set2.emplace(std::piecewise_construct, std::make_tuple(7),
                             std::make_tuple(-70));
                set2.emplace(std::piecewise_construct, std::make_tuple(19),
                             std::make_tuple(-190));

                auto Elem = [](int key, int value) {
                    return Pair(Field(&NonMovableKey::i, key), value);
                };

                EXPECT_THAT(set1, UnorderedElementsAre(Elem(7, -7), Elem(17, -17)));
                EXPECT_THAT(set2, UnorderedElementsAre(Elem(7, -70), Elem(19, -190)));

                // NonMovableKey is neither copyable nor movable. We should still be able to
                // move nodes around.
                static_assert(!std::is_move_constructible<NonMovableKey>::value, "");
                set1.merge(set2);

                EXPECT_THAT(set1,
                            UnorderedElementsAre(Elem(7, -7), Elem(17, -17), Elem(19, -190)));
                EXPECT_THAT(set2, UnorderedElementsAre(Elem(7, -70)));

                auto node = set1.extract(7);
                EXPECT_TRUE(node);
                EXPECT_EQ(node.key().i, 7);
                EXPECT_EQ(node.mapped(), -7);
                EXPECT_THAT(set1, UnorderedElementsAre(Elem(17, -17), Elem(19, -190)));

                auto insert_result = set2.insert(std::move(node));
                EXPECT_FALSE(node);
                EXPECT_FALSE(insert_result.inserted);
                EXPECT_TRUE(insert_result.node);
                EXPECT_EQ(insert_result.node.key().i, 7);
                EXPECT_EQ(insert_result.node.mapped(), -7);
                EXPECT_THAT(*insert_result.position, Elem(7, -70));
                EXPECT_THAT(set2, UnorderedElementsAre(Elem(7, -70)));

                node = set1.extract(17);
                EXPECT_TRUE(node);
                EXPECT_EQ(node.key().i, 17);
                EXPECT_EQ(node.mapped(), -17);
                EXPECT_THAT(set1, UnorderedElementsAre(Elem(19, -190)));

                node.mapped() = 23;

                insert_result = set2.insert(std::move(node));
                EXPECT_FALSE(node);
                EXPECT_TRUE(insert_result.inserted);
                EXPECT_FALSE(insert_result.node);
                EXPECT_THAT(*insert_result.position, Elem(17, 23));
                EXPECT_THAT(set2, UnorderedElementsAre(Elem(7, -70), Elem(17, 23)));
            }

            bool FirstIsEven(std::pair<const int, int> p) { return p.first % 2 == 0; }

            TEST(NodeHashMap, erase_if) {
                // Erase all elements.
                {
                    node_hash_map<int, int> s = {{1, 1},
                                                 {2, 2},
                                                 {3, 3},
                                                 {4, 4},
                                                 {5, 5}};
                    erase_if(s, [](std::pair<const int, int>) { return true; });
                    EXPECT_THAT(s, IsEmpty());
                }
                // Erase no elements.
                {
                    node_hash_map<int, int> s = {{1, 1},
                                                 {2, 2},
                                                 {3, 3},
                                                 {4, 4},
                                                 {5, 5}};
                    erase_if(s, [](std::pair<const int, int>) { return false; });
                    EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3),
                                                        Pair(4, 4), Pair(5, 5)));
                }
                // Erase specific elements.
                {
                    node_hash_map<int, int> s = {{1, 1},
                                                 {2, 2},
                                                 {3, 3},
                                                 {4, 4},
                                                 {5, 5}};
                    erase_if(s,
                             [](std::pair<const int, int> kvp) { return kvp.first % 2 == 1; });
                    EXPECT_THAT(s, UnorderedElementsAre(Pair(2, 2), Pair(4, 4)));
                }
                // Predicate is function reference.
                {
                    node_hash_map<int, int> s = {{1, 1},
                                                 {2, 2},
                                                 {3, 3},
                                                 {4, 4},
                                                 {5, 5}};
                    erase_if(s, FirstIsEven);
                    EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
                }
                // Predicate is function pointer.
                {
                    node_hash_map<int, int> s = {{1, 1},
                                                 {2, 2},
                                                 {3, 3},
                                                 {4, 4},
                                                 {5, 5}};
                    erase_if(s, &FirstIsEven);
                    EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
                }
            }

        }  // namespace
    }  // namespace container_internal

}  // namespace abel
